using System;
using System.Xml;

namespace XmlObjects
{
	/// <summary>
	/// Reads & writes data in xml file with XPath that doesn't exists yet.
	/// </summary>
	public class XPathStore
	{
		protected XmlDocument doc;

		public XPathStore()
		{
			doc = new XmlDocument();
		}

		public XPathStore(XmlDocument doc)
		{
			this.doc = doc;
		}


		public XmlDocument Document
		{
			get { return doc; }
		}

		/// <summary>
		/// Get a xml node from xPath, if it doesn,t exists create it.
		///
		/// __________ EXAMPLES ____________
		///  FdisSettings/User[@Name="FDIS_DTO"]/@AutoHide
		///  FdisSettings/User[@Name="FDIS_DTO"]/ReportFilter/Report[@Name="Afvoermeldingen per Categorie"]/@ReloadThisSettings
		/// </summary>
		/// <param name="sXPath"></param>
		/// <returns></returns>
		public XmlNode QueryCreatNode(string sXPath, XmlNode node)
		{

			XmlNodeList list = node.SelectNodes(sXPath);
			if (list.Count == 1)
				return list.Item(0); // The easy way (The node exists)
			else if (list.Count == 0)
			{
				// Parses XPath in separate parts.
				bool bParsingRandomText = false;
				int nXpathPartStart = -1;

				for (int i = 0; i < sXPath.Length; i++)
				{
					if (!bParsingRandomText &&
					    ((sXPath[i] == '/' || sXPath[i] == '\0') || i == sXPath.Length - 1) &&
					    nXpathPartStart != -1)
					{
						if (i == sXPath.Length - 1)
							i++; // Include last caracter

						// handle this XPath part
						node = QueryCreateNode(node, sXPath.Substring(nXpathPartStart, i - nXpathPartStart));

						nXpathPartStart = -1;
					}
					else if (sXPath[i] == '\"')
						bParsingRandomText = !bParsingRandomText; //  After '"' every character is possible, even '/'
					else if (nXpathPartStart == -1)
						nXpathPartStart = i;
				}
			}
			else
			{
				throw new ParserException(
					string.Format("ERROR   Xpath:{0}  returns {1} nodes\n	Change XPath or XML so it returns ony one node", sXPath,
					              list.Count));
			}

			return node;
		}


		// Create the required node for XPath
		protected XmlNode QueryCreateNode(XmlNode node, string sXPathPart)
		{
			if (node == null)
				node = doc;

			//------------------------------------------------------------------------------------
			// Step 1)	Handle select part
			int nNodeStart = -1;
			int nAttrStart = -1;
			int nLoc = 0;

			for (; nLoc < sXPathPart.Length; nLoc++)
			{
				if (nAttrStart == -1 && nNodeStart == -1)
				{
					if (sXPathPart[nLoc] == '@')
						nAttrStart = nLoc + 1;
					else
						nNodeStart = nLoc;
				}

				if (sXPathPart[nLoc] == '[' || nLoc == sXPathPart.Length - 1)
					break;
			}
			if (nLoc == sXPathPart.Length - 1)
				nLoc++; // Include last caracter


			if (nNodeStart != -1)
			{
				// Query child Node
				XmlNodeList NodeList = node.SelectNodes(sXPathPart);
				if (NodeList.Count == 1)
					return NodeList.Item(0);
				else if (NodeList.Count == 0)
				{
					node = node.AppendChild(doc.CreateElement(sXPathPart.Substring(nNodeStart, nLoc - nNodeStart)));
				}
				else
					throw new ParserException(
						string.Format("ERROR   XpathPart:{0}  returns {1} nodes\n	Change XPath or XML so it returns ony one node",
						              sXPathPart, NodeList.Count));
			}
			else if (nAttrStart != -1)
			{
				// Get Attribute
				XmlAttributeCollection AttrCol = node.Attributes;
				node = AttrCol.GetNamedItem(sXPathPart.Substring(nAttrStart, nLoc - nAttrStart));
				if (node == null)
				{
					node = AttrCol.SetNamedItem(doc.CreateAttribute(sXPathPart.Substring(nAttrStart, nLoc - nAttrStart)));
				}
			}


			//------------------------------------------------------------------------------------
			// Step 2)	Handle filter parts
			int nAttrNameStart = -1;
			int nAttrTextStart = -1;

			for (; nLoc < sXPathPart.Length; nLoc++)
			{
				if (sXPathPart[nLoc] == '@')
					nAttrNameStart = nLoc + 1;
				else if (sXPathPart[nLoc] == '\"')
				{
					if (nAttrTextStart == -1)
						nAttrTextStart = nLoc + 1;
					else
					{
						XmlNode attrNode = node.Attributes.GetNamedItem(sXPathPart.Substring(nAttrNameStart, nLoc - nAttrNameStart));
						if (attrNode == null)
						{
							attrNode = doc.CreateAttribute(sXPathPart.Substring(nAttrNameStart, nAttrTextStart - nAttrNameStart - 2));
							attrNode.Value = sXPathPart.Substring(nAttrTextStart, nLoc - nAttrTextStart);
							node.Attributes.SetNamedItem(attrNode);
						}

						nAttrNameStart = -1;
						nAttrTextStart = -1;
					}
				}
				else if (sXPathPart[nLoc] == ']')
					break;
			}

			return node;
		}
	}
}